--[[World
TC世界主类
用于整个世界的管理
]]
World = class()

-- 创建一个新的空世界，输入世界大小，长宽要为16的倍数
function World:init(worldInfo)
    -- 长度，宽度，高度
    self.length = worldInfo.length
    self.height = worldInfo.height
    self.name = worldInfo.name
    self.createdTime = worldInfo.createdTime
    self.seed = worldInfo.seed
    Chunk.Height = self.height
    -- 世界实际坐标边界范围
    self.x0 = -self.length // 2
    self.x1 = self.length // 2 - 1
    
    self.spawnX,self.spawnZ = worldInfo.spawnX,worldInfo.spawnZ

    self.worldRenderer = nil
    -- 世界中的实体列表
    self.entityList = EntityList()
    self.entityBlockList = EntityBlockList()
    self.needTickBlockList = {}
    -- 在下一次随机更新之前的tick数
    self.ticksBeforeRandomTick = 0
    -- 时间的时间(从创建到现在的tick数)
    self.time = worldInfo.time
    
    self.chunkManager = ChunkManager(self,worldInfo)
    
    self.brightnessUpdateLevel = 30
    self.brightnessToUpdateList = {}
    
    self.globalColor = color(255, 255, 255, 255)
    self.fogColor = color(186, 204, 217, 255)
end

function World:setWorldRenderer(renderer)
    self.worldRenderer = renderer
end

function World:getWorldInfo()
    local worldInfo = WorldInfo(self.length,self.height,self.name,self.seed,self.createdTime)
    worldInfo.spawnX,worldInfo.spawnZ = self.spawnX,self.spawnZ
    worldInfo.time = self.time
    worldInfo.mode = self.chunkManager.mode
    worldInfo.flatSample = self.chunkManager.flatSample
    worldInfo.lastPlayed = date.getDateString()
    return worldInfo
end

-- 创建新的地图
function World:createNewWorld()
    self.chunkManager:createChunks()
    -- self:updateAllMap()
end

-- 获取指定坐标方块ID
function World:getBlock(x,y,z)
    if y > self.height - 1 or y < 0 then
        return Block.air.id
    elseif x < self.x0 or x > self.x1 or z < self.x0 or z > self.x1 then
        return Block.bedRock.id
    end
    local chunk = self.chunkManager:getChunk(x // 16,z // 16)
    if not chunk then return 0 end
    x = x % 16
    z = z % 16
    return chunk:getBlock(x,y,z)
end

-- 坐标是否在世界内
function World:hasBlock(x,y,z)
    if x < self.x0 or x > self.x1 or y < 0 or y > self.height - 1 or z < self.x0 or z > self.x1 then
        return false
    end
    --[[
    if self.chunkManager:isChunkLoaded(x,z) then
        return true
    end
      ]]
    return true
end

function World:isBlockLoaded(x,z)
    local chunk = self.chunkManager:getChunk(x // 16,z // 16)
    if chunk then
        return true
    else
        return false
    end
end

-- 设置方块并进行方块更新
function World:setBlock(x,y,z,blockid)
    local s,oldBlock = self:setBlockDirectly(x,y,z,blockid)
    if not s then return false end
    
    self:neighborChanged(x-1,y,z,Right,blockid)
    self:neighborChanged(x+1,y,z,Left,blockid)
    self:neighborChanged(x,y-1,z,Top,blockid)
    self:neighborChanged(x,y+1,z,Bottom,blockid)
    self:neighborChanged(x,y,z+1,Back,blockid)
    self:neighborChanged(x,y,z-1,Front,blockid)
    
    if blockid ~= Block.air.id then
        self:blockBeSet(x,y,z,blockid)
    end
    
    self.CUtimer = os.clock()
    self:updateBlock(x,y,z,true)
    return true
end

-- 直接设置方块(此方法速度更快)
function World:setBlockDirectly(x,y,z,blockid)
	 if x < self.x0 or x > self.x1 or y < 0 or y > self.height or z < self.x0 or z > self.x1 then
        return false
    end
    
    local chunk = self.chunkManager:getChunk(x // 16,z // 16)
    if not chunk then return false end
    x = x % 16
    z = z % 16
    local old = chunk:getBlock(x,y,z)
    if old == blockid then
        return false
    end
    chunk:setBlock(x,y,z,blockid)
    chunk.changed = true
    return true,old
end

-- 是否是固体
function World:isSolidBlock(x,y,z)
    local id = self:getBlock(x,y,z)
    return Block.blocks[id].solidBlock
end

-- 是否为空气
function World:isAirBlock(x,y,z)
    return self:getBlock(x,y,z) == 0
end

-- 是否透明
function World:isOpacityBlock(x,y,z)
    local id = self:getBlock(x,y,z)
    return Block.blocks[id].opacityBlock
end

-- 是否为液体
function World:isLiquidBlock(x,y,z)
    local id = self:getBlock(x,y,z)
    return Block.blocks[id].liquidBlock
end

-- 用方块坐标获得区块
function World:getChunkFromBlockCoord(x,z)
    return self.chunkManager:getChunk(x // 16,z // 16)
end

-- 用区块坐标获得区块
function World:getChunkFromChunkCoord(x,z)
    return self.chunkManager:getChunk(x,z)
end

-- 获得AABB范围内的方块
function World:getCubesWithInAABB(aabb)
    local x0 = math.floor(aabb.x0)
    local y0 = math.floor(aabb.y0)
    local z0 = math.floor(aabb.z0)
    local x1 = math.floor(aabb.x1 + 1.0)
    local y1 = math.floor(aabb.y1 + 1.0)
    local z1 = math.floor(aabb.z1 + 1.0)

    local aabbs = {}
    local insert = table.insert
    local chunk
    for x = x0,x1 do
        for y = y0,y1 do
            for z = z0,z1 do
                chunk = self:getChunkFromBlockCoord(x,z)
                if (not chunk) or (not chunk.readyToRender) then
                    insert(aabbs,Block.getAABB(x,y,z))
                elseif self:isSolidBlock(x,y,z) then
                    insert(aabbs,Block.getAABB(x,y,z))
                end
            end
        end
    end
    
    return aabbs
end

-- 世界的周期更新函数
function World:tick()
    local bs = Block.blocks
    local blockid,x,y,z
    
    for i,v in ipairs(self.needTickBlockList) do
        x,y,z = v[1],v[2],v[3]
        blockid = self:getBlock(x,y,z)
        bs[blockid]:tick(x,y,z,true)
        table.remove(self.needTickBlockList,i)
    end
    
    if self.ticksBeforeRandomTick <= 0 then
        local mf,mr = math.floor,math.random
        local bs,block = Block.blocks
        local h = self.height - 1
        x,z = math.floor(player.x),math.floor(player.z)
        local x0,z0,x1,z1 = x - 32,z - 32,x + 32,z + 32
        for i = 1,64 do
            x = mf(mr(x0,x1))
            y = mf(mr(0,h))
            z = mf(mr(z0,z1))
            block = bs[self:getBlock(x,y,z)]
            block:tick(x,y,z)
        end
        self.ticksBeforeRandomTick = 10
    else
        self.ticksBeforeRandomTick = self.ticksBeforeRandomTick - 1 
    end
    self:timeProcess()
end

-- 区块更新处理
function World:updateChunk()
    if player.posUpdated or self.chunkManager.updateFinished == false then
        player.posUpdated = false
        self.chunkManager:updateChunk()
    end
    
    local list = self.brightnessToUpdateList
    if #list > 0 then
        local v = list[#list]
        self:updateBlock(v[1],v[2],v[3])
        list[#list] = nil
    end
end

-- 通知方块改变了(通常时需要重新渲染)
function World:updateBlock(x,y,z,updated)
    if x < self.x0 or x > self.x1 or y < 0 or y > self.height - 1 or z < self.x0 or z > self.x1 then
        return false
    end
    local chunk = self:getChunkFromBlockCoord(x,z)
    if not chunk then return end
    local xa = x % 16
    local za = z % 16
    chunk:updateMap(xa,za,1,1)
    
    local skylighted = y >= self:getHeight(x,z)
    
    -- if Block.blocks[chunk:getBlock(xa,y,za)].opacityBlock or true then
        local br
        if skylighted then
            br = Brightness.Max
        else
            br = math.max(self:getBrightnessAfterDelay(x + 1,y,z),self:getBrightnessAfterDelay(x - 1,y,z),self:getBrightnessAfterDelay(x,y + 1,z),self:getBrightnessAfterDelay(x,y - 1,z),self:getBrightnessAfterDelay(x,y,z + 1),self:getBrightnessAfterDelay(x,y,z - 1),Block.blocks[chunk:getBlock(xa,y,za)].brightness)
        end
        if br < 0 then br = 0 end
        if self:setBrightness(x,y,z,br) then
            updated = true
        end
    --[[else
        local br
        if skylighted then
            br = Brightness.Max
        else
            br = Block.blocks[chunk:getBlock(xa,y,za)].brightness
        end
        if self:setBrightness(x,y,z,br) then
            updated = true
        end
    end]]
    
    if updated then
        if self.brightnessUpdateLevel > 0 then
            if os.clock() - self.CUtimer >= 0.01 then
                Thread.waitWhenGaming()
                self.CUtimer = os.clock()
            end
            self.brightnessUpdateLevel = self.brightnessUpdateLevel - 2
            self:updateBlock(x-1,y,z,false)
            self:updateBlock(x+1,y,z,false)
            self:updateBlock(x,y,z+1,false)
            self:updateBlock(x,y,z-1,false)
            self.brightnessUpdateLevel = self.brightnessUpdateLevel + 1
            self:updateBlock(x,y-1,z,false)
            self:updateBlock(x,y+1,z,false)
            self.brightnessUpdateLevel = self.brightnessUpdateLevel + 1
        else
            table.insert(self.brightnessToUpdateList,{x + 1,y,z})
            table.insert(self.brightnessToUpdateList,{x - 1,y,z})
            -- table.insert(self.brightnessToUpdateList,{x,y - 1,z})
            -- table.insert(self.brightnessToUpdateList,{x,y + 1,z})
            table.insert(self.brightnessToUpdateList,{x,y,z + 1})
            table.insert(self.brightnessToUpdateList,{x,y,z - 1})
        end
        if self.worldRenderer then 
            self.worldRenderer:blockChanged(x,y,z) 
        end
    end
end
    
function World:getBrightnessAfterDelay(x,y,z)
    if y >= self.height then
        return Brightness.Max
    elseif x < self.x0 or x > self.x1 or y < 0 or z < self.x0 or z > self.x1 then
        return 0
    else
        local chunk = self:getChunkFromBlockCoord(x,z)
        if not chunk then return 0 end
        x = x % 16
        z = z % 16
        return chunk:getBrightness(x,y,z) - Block.blocks[chunk:getBlock(x,y,z)].lightDelay
    end
end

--  更新指定范围内的高度图和亮度图
function World:updateMap(x0,z0,x1,z1)
    local xl0 = x0 // 16
    local zl0 = z0 // 16
    local xl1 = x1 // 16 + 1
    local zl1 = z1 // 16 + 1
    
    for x = xl0,xl0 + xl1 do
        for z = zl0,zl0 + zl1 do
            local chunk = self:getChunkFromChunkCoord(x,z)
            if chunk ~= nil then
                chunk:updateAllMap()
            end
        end
    end
    
    self:updateBrightness(x0,z0,x1,z1)
end

-- 更新全部高度图和亮度图
function World:updateAllMap()
    self:updateMap(self.x0,self.x0,self.length,self.length)
end

function World:getHeight(x,z)
    if x < self.x0 or x > self.x1 or z < self.x0 or z > self.x1 then
        return 0
    end
    local chunk = self:getChunkFromBlockCoord(x,z)
    if not chunk then return 0 end
    x = x % 16
    z = z % 16
    return chunk:getHeight(x,z)
end

function World:getBrightness(x,y,z)
    if y < 0 then
        return 0
    elseif x < self.x0 or x > self.x1 or y >= self.height or z < self.x0 or z > self.x1 then
        return Brightness.Max
    end
    local chunk = self:getChunkFromBlockCoord(x,z)
    if not chunk then return Brightness.Max end
    x = x % 16
    z = z % 16
    return chunk:getBrightness(x,y,z)
end

function World:setBrightness(x,y,z,brightness)
    if x < self.x0 or x > self.x1 or y < 0 or y > self.height or z < self.x0 or z > self.x1 then
        return false
    end
    local chunk = self:getChunkFromBlockCoord(x,z)
    if not chunk then return false end
    x = x % 16
    z = z % 16
    if chunk:getBrightness(x,y,z) == brightness then
        return false
    end
    chunk:setBrightness(x,y,z,brightness)
    return true
end

-- 当相邻方块被改变时的通知
function World:neighborChanged(x,y,z,face,block)
    tile = Block.blocks[self:getBlock(x,y,z)]
    if tile ~= Block.air then
        tile:neighborChanged(x,y,z,face,block)
    end
end

-- 指定位置是否没有实体
function World:isFree(aabb)
    for k,entity in pairs(self.entityList.list) do
        if aabb:intesrsects(entity.aabb) then
            return false
        end
    end
    if aabb:intesrsects(player.aabb) then
        return false
    end
    return true
end

-- 放置一个新方块的通知
function World:blockBeSet(x,y,z,blockid)
    Block.blocks[blockid]:blockBeSet(x,y,z)
end

function World:markBlockNeedTick(x,y,z)
    table.insert(self.needTickBlockList,{x,y,z})
end

function World:hasBlockNeedTick()
    if #self.needTickBlockList > 0 then
        return true
    else
        return false
    end
end

function World:containLiquid(aabb,liquidBlockid)
    local x0 = math.floor(aabb.x0)
    local y0 = math.floor(aabb.y0)
    local z0 = math.floor(aabb.z0)
    local x1 = math.floor(aabb.x1 + 1.0)
    local y1 = math.floor(aabb.y1 + 1.0)
    local z1 = math.floor(aabb.z1 + 1.0)
    
    if x0 < self.x0 then x0 = self.x0 end
    if z0 < self.x0 then z0 = self.x0 end
    if y0 < 0 then y0 = 0 end
    if x1 > self.x1 then x1 = self.x1 end
    if z1 > self.x1 then z1 = self.x1 end
    if y1 > self.height - 1 then y1 = self.height - 1 end

    local bs = Block.blocks
    local b
    for x = x0,x1 do
        for y = y0,y1 do
            for z = z0,z1 do
                b = bs[self:getBlock(x,y,z)]
                if b.liquidBlock and b.id == liquidBlockid then
                    return true
                end
            end
        end
    end
    
    return false
end

function World:containAnyLiquid(aabb)
    local x0 = math.floor(aabb.x0)
    local y0 = math.floor(aabb.y0)
    local z0 = math.floor(aabb.z0)
    local x1 = math.floor(aabb.x1 + 1.0)
    local y1 = math.floor(aabb.y1 + 1.0)
    local z1 = math.floor(aabb.z1 + 1.0)
    
    if x0 < self.x0 then x0 = self.x0 end
    if z0 < self.x0 then z0 = self.x0 end
    if y0 < 0 then y0 = 0 end
    if x1 > self.x1 then x1 = self.x1 end
    if z1 > self.x1 then z1 = self.x1 end
    if y1 > self.height - 1 then y1 = self.height - 1 end

    local bs = Block.blocks
    local b
    for x = x0,x1 do
        for y = y0,y1 do
            for z = z0,z1 do
                b = bs[self:getBlock(x,y,z)]
                if b.liquidBlock then
                    return true
                end
            end
        end
    end
    
    return false
end

function World:getTime()
    return self.time
end

function World:updateBrightness(x0,z0,x1,z1)
    local biome
    self.CUtimer = os.clock()
    for x = x0,x0 + x1 - 1 do
        for z = z0,z0 + z1 - 1 do
            biome = self.chunkManager:getBiomeAt(x,z)
            if biome.shouldUpdateBrightness then
                for y = self:getHeight(x,z) - 1,0,-4 do
                    -- if self:isOpacityBlock(x,y,z) then
                    self:updateBlock(x,y,z)
                    -- break
                    -- end
                end
            end
        end
    end
end

-- 处理时间，昼夜变化
-- 一天为 20x60x12 = 14400(实际时间12分钟)
function World:timeProcess()
    self.time = self.time + 1
    if self.time >= 14400 then
        self.time = self.time - 14400
    end
    if self.time <= 6600 then
        self.globalColor = color(255, 255, 255, 255)
        self.fogColor = color(186, 204, 217, 255)
    elseif self.time <= 7800 then
        self.globalColor = color(192, 157, 157, 255)
        self.fogColor = color(158, 134, 127, 255)
    elseif self.time >= 13200 then
        self.globalColor = color(212, 190, 190, 255)
        self.fogColor = color(161, 139, 130, 255)
    else
        self.globalColor = color(38, 44, 50, 255)
        self.fogColor = color(29, 32, 35, 255)
    end
end
